import BigNumber from 'bignumber.js';
import { dayjs } from 'element-plus';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone ';
import localizedFormat from 'dayjs/plugin/localizedFormat';
dayjs.extend(localizedFormat);
dayjs.extend(utc);
dayjs.extend(timezone);

/** 支持时间格式及字符串格式输出YYYY-MM-DDTHH:mm:ssZ  YYYY-MM-DD HH:mm:ss ** /

/** 普通日期格式化
 * @param {string | number} date 时间
 * @param {string} format 格式化
 * @param {any} default_text 默认return内容
 * @returns {string}
 */
export const formatDate = (
  date,
  format = 'YYYY-MM-DD HH:mm:ss',
  default_text = ' - '
) => {
  if (!date || date.toString().indexOf('0001-') > -1) return default_text;
  return dayjs(date).format(format);
};

/** 按时区日期格式化
 * @param {string | number | Date} date 时间
 * @param {string} format 格式化
 * @param {Object} options 其他选项参数timezone:时区,默认+8时区 default_text:默认return内容
 * @returns {string}
 */
export const timezoneFormatDate = (
  date,
  format = 'YYYY-MM-DD HH:mm:ss',
  options
) => {
  const { timezone = 'Asia/Shanghai', default_text = ' - ' } = options;
  dayjs.tz.setDefault(timezone); // 设置为默认时区
  // 创建一个日期对象
  const time = dayjs(date);
  // 转换为指定时区的时间
  const convertedDate = time.tz(timezone);
  // 输出转换后的时间
  if (!date || date.toString().indexOf('0001-') > -1) return default_text;
  return convertedDate.format(format);
};

/** 初始化得到的开始时间和结束时间
 * @param {string | number} date 时间
 * @param {string} type 开始或者结束
 * @param {string} format 格式化
 * @param {string} timezone 时区 默认+8时区
 * @returns {string}
 */
function initDate(date, type, format, timezone) {
  dayjs.tz.setDefault(timezone); // 设置为默认时区
  // 创建一个日期对象
  const time = dayjs(date);
  // 转换为指定时区的时间
  const convertedDate = time.tz(timezone);
  if (type === 'start') {
    return convertedDate.startOf('day').format(format);
  }
  if (type === 'end') {
    return convertedDate.endOf('day').format(format);
  }
}

/** 初始化快捷得到的开始时间和结束时间
 * @param {string | number} date 时间
 * @param {string} format 格式化
 * @param {string} timezone 时区 默认+8时区
 * @returns {string}
 */
export class dateShortcuts {
  // 最近x天
  static LAST_X_DAYS(
    data = new Date(),
    format = 'YYYY-MM-DD HH:mm:ss',
    day = 7,
    timezone = 'Asia/Shanghai'
  ) {
    const end = dayjs(data);
    const start = end.subtract(day - 1, 'day');
    return [
      initDate(start, 'start', format, timezone),
      initDate(end, 'end', format, timezone)
    ];
  }
  // 本周
  static THIS_WEEK(
    data = new Date(data),
    format = 'YYYY-MM-DD HH:mm:ss',
    timezone = 'Asia/Shanghai'
  ) {
    const end = dayjs(data).endOf('week');
    const start = end.startOf('week');
    return [
      initDate(start, 'start', format, timezone),
      initDate(end, 'end', format, timezone)
    ];
  }
  // 上周
  static PREV_WEEK(
    data = new Date(),
    format = 'YYYY-MM-DD HH:mm:ss',
    timezone = 'Asia/Shanghai'
  ) {
    const end = dayjs(data).startOf('week').subtract(1, 'day');
    const start = end.startOf('week');
    return [
      initDate(start, 'start', format, timezone),
      initDate(end, 'end', format, timezone)
    ];
  }
  // 本月
  static THIS_MONTH(
    data = new Date(),
    format = 'YYYY-MM-DD HH:mm:ss',
    timezone = 'Asia/Shanghai'
  ) {
    const end = dayjs(data).endOf('month');
    const start = end.startOf('month');
    return [
      initDate(start, 'start', format, timezone),
      initDate(end, 'end', format, timezone)
    ];
  }
  // 上月
  static PREV_MONTH(
    data = new Date(),
    format = 'YYYY-MM-DD HH:mm:ss',
    timezone = 'Asia/Shanghai'
  ) {
    const end = dayjs(data).startOf('month').subtract(1, 'day');
    const start = end.startOf('month');
    return [
      initDate(start, 'start', format, timezone),
      initDate(end, 'end', format, timezone)
    ];
  }
}

/**复制
 * @param {numer | string} val
 */
export function clickCopy(val) {
  // 模拟 输入框
  var cInput = document.createElement('input');
  cInput.value = val;
  document.body.appendChild(cInput);
  cInput.select(); // 选取文本框内容
  // 执行浏览器复制命令
  // 复制命令会将当前选中的内容复制到剪切板中（这里就是创建的input标签）
  // Input要在正常的编辑状态下原生复制方法才会生效
  document.execCommand('copy');
  // 复制成功后再将构造的标签 移除
  document.body.removeChild(cInput);
}

/**数据重复判断
 * @param {array } array 数据
 * @param {string | number } key  基于这个属性来检查重复项
 * @param {string | number } value  是否重复的值
 * @returns {boolean}
 */
export function hasDuplicates(array, key, value) {
  // 首先检查array是否是一个数组
  if (isArray(array)) {
    // 如果是json数组
    if (isPlainObject(array?.[0])) {
      // 检查对象数组中是否有对象具有特定的键和值
      return array.some(e => e[key] === value);
    } else {
      // 否则，假设是纯数组并检查是否包含特定的值
      return array.includes(key);
    }
  } else {
    // 如果不是数组，返回false
    return false;
  }
}

/**深拷贝
 * @param {Object | Array} source
 * @returns {Object | Array}}
 */
export function deepClone(source, hash = new WeakMap()) {
  // 日期对象直接返回一个新的日期对象
  if (source instanceof Date) {
    return new Date(source);
  }
  //正则对象直接返回一个新的正则对象
  if (source instanceof RegExp) {
    return new RegExp(source);
  }
  //如果循环引用,就用 weakMap 来解决
  if (hash.has(source)) {
    return hash.get(source);
  }
  // 获取对象所有自身属性的描述
  let allDesc = Object.getOwnPropertyDescriptors(source);
  // 遍历传入参数所有键的特性
  let cloneObj = Object.create(Object.getPrototypeOf(source), allDesc);

  hash.set(source, cloneObj);
  for (let key of Reflect.ownKeys(source)) {
    if (typeof source[key] === 'object' && source[key] !== null) {
      cloneObj[key] = deepClone(source[key], hash);
    } else {
      cloneObj[key] = source[key];
    }
  }
  return cloneObj;
}

/**数字转化为大写金额
 * @param {Number} n
 * @returns {String}}
 */
export function digitUppercase(n) {
  if (typeof n !== 'number') return '';
  const fraction = ['角', '分'];
  const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
  const unit = [
    ['元', '万', '亿'],
    ['', '拾', '佰', '仟']
  ];
  n = Math.abs(n);
  let s = '';
  for (let i = 0; i < fraction.length; i++) {
    s += (
      digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]
    ).replace(/零./, '');
  }
  s = s || '整';
  n = Math.floor(n);
  for (let i = 0; i < unit[0].length && n > 0; i++) {
    let p = '';
    for (let j = 0; j < unit[1].length && n > 0; j++) {
      p = digit[n % 10] + unit[1][j] + p;
      n = Math.floor(n / 10);
    }
    s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s;
  }
  return s
    .replace(/(零.)*零元/, '元')
    .replace(/(零.)+/g, '零')
    .replace(/^整$/, '零元整');
}

/**数字转千分位
 * @param {numer | string} mount
 * @param {numer} precision 保留几位小数，4舍5入，默认为空,可传0 roundToFixed
 * @param {boolean} placeholder 是否需要位数不够补位
 * @returns {string}
 */
export function toThousands(mount, precision, placeholder) {
  if (mount) {
    let res = 0;
    if (precision || precision === 0)
      res = roundToFixed(mount, precision, placeholder);
    else res = mount;
    res = res.toString().replace(/\d+/, function (num) {
      // 先提取整数部分
      return num.replace(/(\d)(?=(\d{3})+$)/g, function ($1) {
        return $1 + ',';
      });
    });
    if (precision) {
      return res;
    } else {
      return res;
    }
  } else {
    return mount;
  }
}

/**4舍5入
 * 数字乘以10的n次方（n为保留的小数位数），然后使用Math.round()方法进行四舍五入，最后再除以10的n次方
 * @param {numer | string} num
 * @param {numer} precision 小数点位数
 * @param {boolean} placeholder 是否需要位数不够补位
 * @returns {number | string}console.log(toFixed(1.005, 2));  // 输出 1.01
 */
export function roundToFixed(num, precision = 2, placeholder = false) {
  const roundedNumber =
    Math.round(+num + 'e' + precision) / Math.pow(10, precision);
  if (placeholder) {
    let fixedNumber = roundedNumber.toString();
    const decimalIndex = fixedNumber.indexOf('.');
    const decimalPart =
      decimalIndex === -1 ? '' : fixedNumber.slice(decimalIndex + 1);

    if (decimalPart.length < precision) {
      fixedNumber += '.' + decimalPart.padEnd(precision, '0');
    }
    return fixedNumber;
  } else {
    return roundedNumber;
  }
}

/**首字母大写
 * @param {String} str
 * @returns {string}
 */
export function titleCase(str) {
  return str.replace(/( |^)[a-z]/g, L => L.toUpperCase());
}

/**下划转驼峰
 * @param {String} str
 * @returns {string}
 */
export function camelCase(str) {
  return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase());
}

/** 文件下载
 * @param {file} blob 返回的文件流
 * @param {string} name 文件名字
 * @param {string} fileType 文件类型
 * @param {string} type 类型
 * @returns {file}
 */
export function downloadFile(blob, name, fileType, type) {
  const _blob = new Blob([blob], {
    type
  });
  const fileName = `${name}.${fileType}`;
  const link = document.createElement('a'); //创建a标签
  link.download = fileName; //a标签添加属性
  link.style.display = 'none';
  link.href = URL.createObjectURL(_blob);
  document.body.appendChild(link);
  link.click(); //执行下载
  URL.revokeObjectURL(link.href); //释放url
  document.body.removeChild(link); //释放标签
}

/** 图片下载
 * @param {Array} urls 图片的 URL 数组
 * @param {string} name 下载后的文件名
 */
export function downloadImgs(urls, name) {
  urls.forEach(link => {
    const image = new Image();
    // 解决跨域问题
    image.setAttribute('crossOrigin', 'anonymous');

    image.onload = () => {
      const canvas = document.createElement('canvas');
      canvas.width = image.width;
      canvas.height = image.height;

      const context = canvas.getContext('2d');
      context.drawImage(image, 0, 0, image.width, image.height);

      const url = canvas.toDataURL('image/png'); // 图片 base64
      const a = document.createElement('a');
      //  + '.png' 定义文件拓展名、防止因为name中有.号导致.png文件拓展名丢失
      a.download = name + '.png';
      a.href = url;

      const event = new MouseEvent('click');
      a.dispatchEvent(event);
    };

    //加时间戳防止图片缓存报跨域问题
    image.src =
      link +
      (link.indexOf('?') > -1 ? '&time=' : '?time=') +
      new Date().valueOf();
  });
}

/**是否是函数
 * @param {any} value
 * @returns {boolean}
 */
export const isFunction = value => {
  const keyType = Object.prototype.toString.call(value);
  return keyType.includes('Function');
};

/**是否是数组
 * @param {any} value
 * @returns {boolean}
 */
export const isArray = value => {
  const keyType = Object.prototype.toString.call(value);
  return keyType.includes('Array');
};
/**是否是纯对象
 * @param {object} obj
 * @returns {boolean}
 */
export const isPlainObject = obj => {
  return Object.prototype.toString.call(obj) === '[object Object]';
};

/**验证只有数字字母
 * @param {any} value
 * @returns {boolean}
 */
export function isNumberStr(value) {
  return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(value);
}
/**只能是整数包含0
 * @param {any} value
 * @returns {boolean}
 */
export function isNumeric(value) {
  return /^\d+$/.test(value);
}

/**只能有字母
 * @param {any} value
 * @returns {boolean}
 */
export function isAlphabetic(value) {
  return /^[A-Za-z]+$/.test(value);
}

/**只能输入不超过x小数
 * @param {any} value
 * @param {Number} decimalPlaces 位数
 * @returns {boolean}
 */
export function isSpecificDecimal(value, decimalPlaces = 2) {
  const Reg = new RegExp(`^[+-]?\\d+(\\.\\d{1,${decimalPlaces}})?$`);
  return Reg.test(value);
}

/**获取url上参数
 * @param {string} url
 * @returns {Object}
 */
export function paramObj(url) {
  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ');
  if (!search) {
    return {};
  }
  const obj = {};
  const searchArr = search.split('&');
  searchArr.forEach(v => {
    const index = v.indexOf('=');
    if (index !== -1) {
      const name = v.substring(0, index);
      const val = v.substring(index + 1, v.length);
      obj[name] = val;
    }
  });
  return obj;
}

/**判断元素是否存在
 * Check if an element has a class
 * @param {HTMLElement} elm
 * @param {string} cls
 * @returns {boolean}
 */
function hasClass(ele, cls) {
  return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
}
/**
 * Add class to element
 * @param {HTMLElement} elm
 * @param {string} cls
 */
export function addClass(ele, cls) {
  if (!ele) return;
  if (!hasClass(ele, cls)) ele.className += ' ' + cls;
}

/**
 * Remove class from element
 * @param {HTMLElement} elm
 * @param {string} cls
 */
export function removeClass(ele, cls) {
  if (!ele) return;
  if (hasClass(ele, cls)) {
    const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
    ele.className = ele.className.replace(reg, ' ');
  }
}

/*********数据的基本运算加减乘除************/

/**加
 * @param {Array} data
 * @returns {number}
 */
export function countPlus(data) {
  if (!data) return 0;
  let sum = 0;
  data.forEach(e => {
    sum = new BigNumber(e).plus(sum).toNumber();
  });
  return sum;
}
/**减
 * @param {Array} data
 * @returns {number}
 */
export function countMinus(data) {
  if (!data) return 0;
  let sum = 0;
  data.forEach((e, index) => {
    if (index === 0) sum = e;
    else sum = new BigNumber(sum).minus(e).toNumber();
  });
  return sum;
}
/**乘
 * @param {Array} data
 * @returns {number}
 */
export function countTimes(data) {
  if (!data) return 0;
  let sum = 0;
  data.forEach((e, index) => {
    if (index === 0) sum = e;
    else sum = new BigNumber(sum).times(e).toNumber();
  });
  return sum;
}
/**除
 * @param {Array} data
 * @returns {number}
 */
export function countDiv(data) {
  if (!data) return 0;
  let sum = 0;
  data.forEach((e, index) => {
    if (index === 0) sum = e;
    else sum = new BigNumber(sum).div(e).toNumber();
  });
  return sum;
}
/**取余
 * @param {number} a1
 * @param {number} a2
 * @returns {number}
 */
export function countMod(a1, a2) {
  return new BigNumber(a1).mod(a2).toNumber();
}

/*********常用正则表达式************/

/**校验只包含字母、数字和下划线 */
export const alNumUndReg = /^\w+$/;

/**校验只包含字母、数字和连字符 */
export const alNumHyReg = /^[a-zA-Z0-9-]+$/;

/**校验包含至少一个字母和一个数字 */
export const numLetReg = /^(?=.*[A-Za-z])(?=.*[0-9])[A-Za-z0-9]+$/;

/**校验只包含字母、数字和空格 */
export const alNumSpReg = /^[A-Za-z0-9\s]+$/;

/**校验只包含字母和空格（可以用于校验真实姓名 */
export const letSpReg = /^[A-Za-z\s]+$/;

/**校验用户名（字母开头，允许5-16字节，允许字母数字下划线） */
export const usernameReg = /^[a-zA-Z][a-zA-Z0-9_]{4,15}$/;

/**校验只包含特殊字符 */
export const spCharReg = /^[^A-Za-z0-9]+$/;

/**校验至少一个特殊字符 */
export const spChkReg = /[^A-Za-z0-9]+/;

/**校验是否包含特殊字符 */
export const hasSpReg = /[ `!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/;

/**校验只包含小写字母 */
export const lowReg = /^[a-z]+$/;

/**校验至少一个小写字母 */
export const hasLowReg = /[a-z]+/;

/**校验只包含大写字母 */
export const upReg = /^[A-Z]+$/;

/**校验至少一个大写字母 */
export const hasUpReg = /[A-Z]+/;

/**校验只包含字母 */
export const letReg = /^[A-Za-z]+$/;

/**校验只包含数字 */
export const numReg = /^[0-9]+$/;

/**校验至少一个数字 */
export const hasNumReg = /\d+/;

/**校验是否为数字 */
export const isNumReg = /^[0-9]*$/;

/**校验是否为小数（匹配非零的小数） */
export const isDecReg = /^[0-9]*\.[0-9]*[1-9]+$/;

/**校验只包含字母和数字 */
export const alNumReg = /^[A-Za-z0-9]+$/;

/**校验连续的空白字符 */
export const consSpaceReg = /\s+/g;

/**检查字符串是否只包含空格 */
export const spaceReg = /^\s*$/;

/**校验十六进制颜色代码 */
export const hexColReg = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;

/**校验音频文件扩展名（例如：.mp3, .wav, .flac） */
export const audExtReg = /\.(mp3|wav|flac)$/i;

/**校验视频文件扩展名（例如：.mp4, .mkv, .flv, .avi） */
export const vidExtReg = /\.(mp4|mkv|flv|avi)$/i;

/**校验图像文件扩展名（例如：.jpg, .jpeg, .png, .gif, .bmp） */
export const imgExtReg = /\.(jpe?g|png|gif|bmp)$/i;

/**校验Github仓库名 */
export const gitRepoReg = /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i;

/**校验货币格式 */
export const currencyReg = /^\d+(?:\.\d{0,2})$/;

/**电子邮件地址 */
export const emailReg =
  /^([a-zA-Z0-9_\.-]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,8})(\.[a-zA-Z]{2,8})?$/;

/**校验中文/汉字 */
export const cnCharReg = /^[\u4e00-\u9fa5]{0,}$/;

/**校验强密码（必须包含大小写字母和数字的组合，不能使用特殊字符，长度在8-10之间） */
export const strPwdReg = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$/;

/**校验密码强度（至少8个字符，包括一个大写字母，一个小写字母和一个数字） */
export const pwdStrReg = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/;

/**校验日期格式（YYYY-MM-DD） */
export const dateReg = /^\d{4}-\d{1,2}-\d{1,2}$/;

/**是用来匹配十六进制颜色值的，它可以匹配如 #ffffff，#abc，#123456，fff，123 这样的字符串，其中 # 是可选的，后面可以是3或6位的十六进制数字 */
export const hexNumReg = /^#?([a-f0-9]{6}|[a-f0-9]{3})$/;

/**校验HTML标签 */
export const htmlTagReg = /<("[^"]*"|'[^']*'|[^'">])*>/;

/**网址（URL） */
export const urlReg = /^(http|https):\/\/[^ "]+$/;

/**校验身份证号（中国） */
export const idCardReg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;

/**校验手机号码（中国） */
export const mobileReg = /^1[34578]\d{9}$/;
